<?php

/***
 * This class defines a Bayes Filter
 * To determine the probability of a feature set belonging to a category:
 * 
 *       P(category|feature_set)
 * 
 * 
 */
class BayesFilter
{
	
	protected  $filter_data_model				= NULL;		// the model where the filter data is stored
	protected  $category_data_model				= NULL;		// the model where the categories for the filter are stored
	protected  $map_data_model					= NULL;		// the cateogry-filter mappings model
	protected  $feature_data_model				= NULL;		// the model where the features are stored
	
	protected  $laplacian_smoother_constant     = 1;
	
	/**
	 * This function sets the Filter required models
	 */
	public function setup_models( Bayes_filter_data_Model $filter_data_model )
	{
		
		$this->filter_data_model	= $filter_data_model;
		
		$setup_arguments			= array($filter_data_model->bayes_category_data_table_name, 
										$filter_data_model->filter_tables_db_name);
		$this->category_data_model	= BayesDataTemplate::factory('Bayes_category_data_Model', $setup_arguments);
		
		$setup_arguments			= array($filter_data_model->bayes_map_data_table_name, 
										$filter_data_model->filter_tables_db_name);
		$this->map_data_model		= BayesDataTemplate::factory('Bayes_map_data_Model', $setup_arguments);
		
		$setup_arguments			= array($filter_data_model->bayes_feature_data_table_name, 
										$filter_data_model->filter_tables_db_name);
		$this->feature_data_model	= BayesDataTemplate::factory('Bayes_feature_data_Model', $setup_arguments);
		
		return TRUE;
	}
	
	
	/**
	 * Increment the counters for the features and categories depending on a feature set
	 * 
	 * @param BayesFeatureSet $features
	 * @param BayesCategoryData $category 
	 */
	
	protected function learn_featureset_for_category(	BayesFeatureSet					$feature_set, 
											Bayes_category_data_Model		$category			)
	{
		
		// create features that dont exist from the feature set and increment counters by one
		$feature_set->increment_counters();
		
		// update the feature_category map counters
		$this->map_data_model->increment_features_category_counters($category, $feature_set);
		
		
		
		$category_feature_count = $this->map_data_model->get_feature_count_for_category($category);
		
		// Update the category counters
		$category->increment_category_message_count($save = FALSE);
		$category->update_category_feature_count( $category_feature_count, $save = TRUE );
		
		// update filter coutners
		$total_feature_count	= $this->feature_data_model()->get_total_features();
		$total_message_count	= $this->category_data_model()->get_total_messages();
		
		$this->filter_data_model->total_feature_count	= $total_feature_count;
		$this->filter_data_model->total_category_count	= $this->category_data_model()->get_total_categories();
		$this->filter_data_model->total_message_count	= $total_message_count;
		
		$this->filter_data_model->save();
		
		return TRUE;
	}
	
	public function learn( $features_array,  $category_name,  $create_category_if_does_not_exist = FALSE)
	{
		
		$category = $this->category_data_model()->set_text_key(array('category_text'=>$category_name));
		if(!$category->exists())
		{
			if($create_category_if_does_not_exist)
			{
				$category->create();
			}
			else
			{
				return FALSE;
			}
		}
		
		$feature_set = new BayesFeatureSet($this->feature_data_model(), $features_array);
		
		return $this->learn_featureset_for_category($feature_set, $category);
		
	}
	
	public function refresh_filter_data_model()
	{
		$this->filter_data_model->set_text_key(array('filter_name'=>$this->filter_data_model->filter_name));
	}
	
	public function calculate_probability_of_categoty_given_featureset(Bayes_category_data_Model $category, BayesFeatureSet	 $feature_set)
	{
		//make sure the filter date is uptodate
		$this->refresh_filter_data_model();
		
		$prior_probability		= $this->calculate_prior_probability($feature_set,$category);	// P({set}|category)
		$category_probability	= $this->calculate_category_probability($category);				// P(category)
		$categories				= $this->category_data_model()->get_all_categories();
		
		$feature_set_probability = 0; // P({set}) to be calculated: the total probability for the feature set.
		
		foreach($categories as $n_category)
		{
			// the total set probability has the prior probability, lets not calculate this twice
			if($n_category->category_text == $category->category_text )
			{
				$feature_set_probability += $prior_probability*$category_probability;
				continue;
			}
			
			$feature_set_probability += $this->calculate_prior_probability($feature_set,$n_category)*$this->calculate_category_probability($n_category);	
		}
			
		
		$return	= ($prior_probability * $category_probability )/ ($feature_set_probability );
		return $return;
	}
	
	public function calculate_category_probability(Bayes_category_data_Model $category)
	{
		$laplacian_k					= $this->laplacian_smoother_constant();
		$number_of_messages_in_category = $category->category_message_count;
		$total_number_of_messages		= $this->filter_data_model->total_message_count;
		$number_of_categories			= $this->filter_data_model->total_category_count;
		
		$return = ($number_of_messages_in_category+$laplacian_k)/($total_number_of_messages+$laplacian_k*$number_of_categories);
		
		return $return;
	}
	
	public function calculate_prior_probability(BayesFeatureSet	 $feature_set, Bayes_category_data_Model $category)
	{
		$result = 1;
		
		
		foreach($feature_set as $feature)
		{
			// this can be cached, and cleared on every train call
			$result *= $this->calculate_probability_of_feature_given_category($feature, $category);
		}
		
		return $result;
	}
	
	public function calculate_probability_of_feature_given_category(Bayes_feature_data_Model $feature, Bayes_category_data_Model $category)
	{
		
		$laplacian_k				= $this->laplacian_smoother_constant();
		$total_filter_features		= $this->filter_data_model->total_feature_count;
		if(!$feature->exists())
		{
			return $laplacian_k/$total_filter_features;
		}
		
		$map							= $this->map_data_model()->find_feature_category($feature,$category);
		
		// if there have been feature-category relationships for this pair get the count, else give 0
		$number_of_times_in_category	= $map->exists()? $map->feature_category_count: 0;
		$feature_total_number_of_times	= $feature->feature_count;
		$probability_feature_given_category = ($number_of_times_in_category + $laplacian_k)/($feature_total_number_of_times + $laplacian_k * $total_filter_features);

		return $probability_feature_given_category;
	}
	
	public function laplacian_smoother_constant()
	{
		return $this->laplacian_smoother_constant;
	}
	
	function category_data_model()
	{
		return $this->category_data_model;
	}
	
	
	function feature_data_model()
	{
		return $this->feature_data_model;
	}
	
	function map_data_model()
	{
		return $this->map_data_model;
	}
	
	
	
	
	
	
	
}
